package com.gitee.xqxyxchy.crypto.impl;

import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import com.gitee.xqxyxchy.crypto.CryptoParam;
import com.gitee.xqxyxchy.crypto.ICryptoService;
import com.gitee.xqxyxchy.crypto.Pair;
import com.gitee.xqxyxchy.crypto.ParamKey;

public class Sm2Crypto implements ICryptoService {

	private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

	private static final String GM_NAMED = "sm2p256v1";
	private static final String SECURE_RANDOM = "SHA1PRNG";
	private static final X9ECParameters x9ECParameters = GMNamedCurves.getByName(GM_NAMED);
	private static final ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(),
			x9ECParameters.getG(), x9ECParameters.getN());
	private static final ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(),
			x9ECParameters.getG(), x9ECParameters.getN());

	@Override
	public String encrypt(String originData, CryptoParam... params) {
		try {
			String publicKey = CryptoParam.Builder.create().get(ParamKey.PUBLIC_KEY.getKey(), params);
			byte[] plainText = originData.getBytes();
			SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
			// 获取一条SM2曲线参数
			ECPoint pukPoint = x9ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
			ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(pukPoint, ecDomainParameters);
			sm2Engine.init(true,
					new ParametersWithRandom(ecPublicKeyParameters, SecureRandom.getInstance(SECURE_RANDOM)));
			byte[] cipherText = sm2Engine.processBlock(plainText, 0, plainText.length);
			return Hex.toHexString(cipherText);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public String decrypt(String encryptedData, CryptoParam... params) {
		try {
			String privateKey = CryptoParam.Builder.create().get(ParamKey.PRIVATE_KEY.getKey(), params);
			BigInteger privateKeyD = new BigInteger(privateKey, 16);
			ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, ecDomainParameters);
			SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C3C2);
			sm2Engine.init(false, privateKeyParameters);
			byte[] decode = Hex.decode(encryptedData);
			byte[] arrayOfBytes = sm2Engine.processBlock(decode, 0, decode.length);
			return new String(arrayOfBytes, DEFAULT_CHARSET);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	//////////////////////////////////////////////////////

	/**
	 * 读取公钥
	 *
	 * @return
	 * @throws Exception
	 */
	public static PublicKey getPublicKey(String pubKeyHex) throws Exception {
		pubKeyHex = unCompressPubKey(pubKeyHex);
		if (pubKeyHex.length() > 128) {
			pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128);
		}
		// 将公钥拆分为x,y分量（各32字节）
		String stringX = pubKeyHex.substring(0, 64);
		String stringY = pubKeyHex.substring(stringX.length());
		// 将公钥x、y分量转换为BigInteger类型
		BigInteger x = new BigInteger(stringX, 16);
		BigInteger y = new BigInteger(stringY, 16);
		// 通过公钥x、y分量创建椭圆曲线公钥规范
		ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y),
				ecParameterSpec);
		// 通过椭圆曲线公钥规范，创建出椭圆曲线公钥对象（可用于SM2加密及验签）
		return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
	}

	/**
	 * 获取密钥
	 *
	 * @return
	 * @throws Exception
	 */
	public static PrivateKey getPrivateKey(String privateKeyHex) throws Exception {
		// 将十六进制私钥字符串转换为BigInteger对象
		BigInteger d = new BigInteger(privateKeyHex, 16);
		// 通过私钥和私钥域参数集创建椭圆曲线私钥规范
		ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecParameterSpec);
		// 通过椭圆曲线私钥规范，创建出椭圆曲线私钥对象（可用于SM2解密和签名）
		return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
	}

	/**
	 * 
	 * 生成SM2公私钥对
	 * 
	 * @return
	 * 
	 */
	private static AsymmetricCipherKeyPair genKeyPair0() {
		// 1.创建密钥生成器
		ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
		// 2.初始化生成器,带上随机数
		try {
			keyPairGenerator
					.init(new ECKeyGenerationParameters(ecDomainParameters, SecureRandom.getInstance(SECURE_RANDOM)));
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}

		// 3.生成密钥对
		AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
		return asymmetricCipherKeyPair;
	}

	/**
	 * 
	 * 生成公私钥对(默认压缩公钥)
	 * 
	 * @return
	 * 
	 */
	public static Pair<String, String> genKeyPair() {
		return genKeyPair(true);
	}

	/**
	 * 
	 * 生成公私钥对
	 * 
	 * @param compressedPubKey 是否压缩公钥
	 * @return
	 * 
	 */
	public static Pair<String, String> genKeyPair(boolean compressedPubKey) {
		AsymmetricCipherKeyPair asymmetricCipherKeyPair = genKeyPair0();
		// 提取公钥点
		ECPoint ecPoint = ((ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic()).getQ();
		// 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
		String pubKey = Hex.toHexString(ecPoint.getEncoded(compressedPubKey));
		BigInteger privatekey = ((ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate()).getD();
		String priKey = privatekey.toString(16);
		return Pair.of(pubKey, priKey);
	}

	/**
	 * 
	 * 将未压缩公钥压缩成压缩公钥
	 * 
	 * @param pubKey 未压缩公钥(16进制,不要带头部04)
	 * @return
	 * 
	 */
	public static String compressPubKey(String pubKey) {
		// 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
		if (pubKey.startsWith("02") || pubKey.startsWith("03")) {
			return pubKey;
		}

		if (!pubKey.startsWith("04")) {
			// 将未压缩公钥加上未压缩标识.
			pubKey = new StringBuilder("04").append(pubKey).toString();
		}
		// 提取公钥点
		ECPoint pukPoint = x9ECParameters.getCurve().decodePoint(Hex.decode(pubKey));
		String compressPubKey = Hex.toHexString(pukPoint.getEncoded(Boolean.TRUE));
		return compressPubKey;
	}

	/**
	 * 
	 * 将压缩的公钥解压为非压缩公钥
	 * 
	 * @param compressKey 压缩公钥
	 * @return
	 * 
	 */
	public static String unCompressPubKey(String compressKey) {
		if (compressKey.startsWith("02") || compressKey.startsWith("03")) {
			// 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
			// 提取公钥点
			ECPoint pukPoint = x9ECParameters.getCurve().decodePoint(Hex.decode(compressKey));
			String pubKey = Hex.toHexString(pukPoint.getEncoded(Boolean.FALSE));
			// 去掉前面的04 (04的时候，可以去掉前面的04)
			pubKey = pubKey.substring(2);
			return pubKey;
		}

		if (compressKey.startsWith("04")) {
			// 去掉前面的04 (04的时候，可以去掉前面的04)
			compressKey = compressKey.substring(2);
		}
		return compressKey;
	}

}
